/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package treegen;

import java.util.ArrayList;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.QuadCurve;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import static treegen.CrownDensity.average;
import static treegen.CrownDensity.dense;
import static treegen.CrownDensity.easy;

/** 
 * Тип формы ветви: прямач, изогнутая вверх или вниз.
 *
 * @author Елена
 */
enum BranchType {
    Right, Up, Down
};

/**
 * Класс ветви логический. Хранит данные о ветви и занимается отрисовкой каждой отдельной ветви в определенном уровне.
 * @author Елена
 */
public class Branch {
    /** Количество уровней ветвей в дереве */
    static int levelCount;
    /** Уровень, к кторому относится данная ветвь */
    int levelNum;
    /** Длина ветви */
    double length;
    /** Ширина ветви */
    double width;
    /** Расстояние между ветками по дереву */
    double radByTrunk;
    /** Количество уровней ветвей в дереве */
    double heightByTrunk;
    /** Угол к стволу */
    double angle;
    /** Количество дочерних ветвей */
    int childrenCount;
    /** Число повторений дочерних ветвей */
    int repetCount;
    /** Высота, на которой должна быть первая ветка уровня */
    double height;
    /** Тип ветви */
    BranchType type;
    /** Тип ветви в строковой форме */
    String strType;
    /** Радиус ствол */
    double radius;
    /** Текстура ветви */
    TrunkTexture texture;
    /** Тип кроны */
    String crownType;
    /** Длина изгиба */
    double curveLength = 0;
    /** Следующий уровень ветвей */
    Branch next;
    /** Высота ствола, по которой должны быть распределены ветви */
    double trunkHeight;
    /** Начальная точка для кривой Безье для изогнутой ветви*/
    Point2D P0;
    /** Средняя точка для кривой Безье для изогнутой ветви */
    Point2D P1;
    /** Конечная точка для кривой Безье для изогнутой ветви */
    Point2D P2;
    /** Начальная точка для прямой ветви */
    Point2D PStart; 
    /** Конечная точка для прямой ветви */
    Point2D PEnd;
    /** Массив листьев */
    public ArrayList<Leaf> leafs = new ArrayList<Leaf>();

    /**
     * Конструктор ветви
     * 
     * @param count количество уровней
     * @param num уровень, к которому принадлежит данная ветвь
     */
    Branch(int count, int num) {
        levelCount = count;
        levelNum = num;
    }

    /**
     * Задать длину ветви
     * 
     * @param length длина ветви
     */
    public void setLength(double length) {
        this.length = length;
    }

    /**
     * Задать ширину ветви
     * 
     * @param width ширина ветви
     */
    public void setWidth(double width) {
        this.width = width;
    }

    /**
     * Задать расстояние между ветками по стволу
     * 
     * @param rad расстояние
     */
    public void setRad(double rad) {
        radByTrunk = rad;
    }

    /**
     * Задать расстояние между ветками по стволу
     * 
     * @param height высота
     */
    public void setHeightByTrunk(double height) {
        heightByTrunk = height;
    }

    /**
     * Установить угол наклона к стволу
     * 
     * @param angle угол
     */
    public void setAngle(double angle) {
        this.angle = angle;
    }

    /**
     * Установить количество дочерних ветвей
     * 
     * @param count количество ветвей
     */
    public void setChildrenCount(int count) {
        childrenCount = count;
    }

    /**
     * Установить количество повторений
     * 
     * @param count количество дочерних ветвей
     */
    public void setRepetCount(int count) {
        repetCount = count;
    }

    /**
     * Установить высоту
     * @param height высота
     */
    public void setHeight(double height) {
        this.height = height;
    }

    /**
     * Установить тип ветви
     * 
     * @param type тип
     */
    public void setType(BranchType type) {
        this.type = type;
    }

    /**
     * Установить количество уровней
     * 
     * @param count количество уровней
     */
    public static void setLevelCount(int count) {
        levelCount = count;
    }

    /**
     * Нарисовать уровень ветвей
     * 
     * @param group группа с изображениями
     * @param tree дерево
     * @param trunk ствол, на котором рисуются ветви
     * @param next следующий уровень
     */
    public void paintLevel(Group group, Tree tree, Trunk trunk, Branch next) {
        this.next = next;
        trunkHeight = trunk.height;
        texture = trunk.texture;
        strType = trunk.strTexture;
        if(trunk.divisions.size() != 0) {
            for (int i = 0; i < trunk.divisions.size(); i++) {
                paintLevel(group, tree, trunk.divisions.get(i), next);
            }
        } else {
            if (trunk.shape == 0) {
                group.getChildren().add(paintCount(getCountOnLevel(trunk, tree.crown), (int) trunk.P0.getX(), (int) ((int) trunk.P0.getY() - height)));
            } else if (trunk.shape == 1) {
                group.getChildren().add(paintCount(getCountOnLevel(trunk, tree.crown), (int) trunk.getX(trunk.P0.getY() - height), (int) ((int) trunk.P0.getY() - height)));
            } else {
                Point2D point = trunk.getPointOnCurve(height / trunk.height);
                group.getChildren().add(paintCount(getCountOnLevel(trunk, tree.crown), (int) point.getX(), (int) point.getY()));
            }
        }
    }

    // Рассчитать колво-во ветвей на одном обхвате дерева
    public int getCountOnLevel(Trunk trunk, Crown crown) {
        double l = 2 * Math.PI * trunk.getWidth(); // длина дуги обхвата
        int count = (int) (l / radByTrunk);
        radius = trunk.getWidth();
        texture = trunk.getTexture();
        crownType = crown.TYPE;
        return count;
    }

    /**
     * Нарисовать определенное количество ветвей
     * 
     * @param count количество
     * @param startX координата x начальной точки для отрисовки
     * @param startY координата y начальной точки для отрисовки
     * @return группа, изображений ветвей
     */
    public Group paintCount(int count, int startX, int startY) {
        Group result = new Group();
        double newB;
        double angle;
        angle = 360 / count;
        int x;
        int y;
        double old;
        double paintHeight;
        if (next != null) {
            paintHeight = next.height - height;
        } else {
            paintHeight = trunkHeight - height;
        }
        for (int j = 0; j < (int) paintHeight; j = (int) (j + heightByTrunk)) {
            for (double i = 0; i < 360; i = i + angle) {
                x = (int) (radius * Math.cos(Math.toRadians(i)) + startX);
                y = (int) (radius / 2 * Math.sin(Math.toRadians(i)) + startY - j);
                Group tmp = paint(x, y);
                tmp.translateZProperty().bind(new SimpleDoubleProperty());
                if (!(i > 90 && i < 270)) {
                    old = Center.maxX(tmp.getBoundsInLocal());
                } else {
                    old = Center.minX(tmp.getBoundsInLocal());
                }

                tmp.setRotationAxis(new Point3D(0, 1, 0));

                tmp.setRotate(i);
                if (!(i > 90 && i < 270)) {
                    newB = Center.maxX(tmp.getBoundsInLocal());
                } else {
                    newB = Center.minX(tmp.getBoundsInLocal());
                }
                if (!(i > 90 && i < 270)) {
                    tmp.setTranslateX(-(radius + length - (old - newB)));
                    Group tmp1 = new Group();
                    tmp1.getChildren().add(tmp);
                    tmp1.setRotationAxis(new Point3D(1, 0, 0));
                    tmp1.setRotate(180);
                    Group tmp2 = new Group();
                    tmp2.getChildren().add(tmp1);
                    tmp2.setRotationAxis(new Point3D(0, 0, 1));
                    tmp2.setRotate(90);
                    tmp = tmp2;
                } else {
                    tmp.setTranslateX(-Math.abs(old - newB));
                }
                Group nextRotate = new Group();
                nextRotate.getChildren().add(tmp);
                nextRotate.translateZProperty().bind(new SimpleDoubleProperty());

                nextRotate.setRotationAxis(new Point3D(0, 0, 1));

                nextRotate.setRotate(90 + angle);
                result.getChildren().add(nextRotate);
            }
        }
        return result;
    }

    /**
     * Нарисовать горизонтальную ветку
     * @param x координата x начальной точки для отрисовки
     * @param y координата x начальной точки для отрисовки
     * @param length длина 
     * @param image изображение для заливки
     * @return прямоугольник, предсавляющий собой горизонтальную ветвь
     */
    public Rectangle paintHorizontal(int x, int y, double length, Image image) {
        Rectangle horizontal;
        horizontal = new Rectangle();
        horizontal.setX(x);
        horizontal.setY(y - width);
        horizontal.setWidth(length + width);
        horizontal.setHeight(width);
        horizontal.setFill(new ImagePattern(image, 0, 0, 100, 100, false));
        PStart = new Point2D(x, y - width);
        PEnd = new Point2D(x + length + width, y);
        return horizontal;
    }

    /**
     * Нарисовать вертикальную ветвь
     * @param x координата x начальной точки для отрисовки
     * @param y координата y начальной точки для отрисовки
     * @param length длина 
     * @param image изображение для заливки
     * @return прямоугольник, предсавляющий собой вертикальную ветвь
     */
    public Rectangle paintVertical(int x, int y, double length, Image image, boolean up) {
        Rectangle vertical;
        vertical = new Rectangle();
        vertical.setX(x - width);
        if (up) {
            vertical.setY(y - length);
            PStart = new Point2D(x - width, y - length);
        } else {
            vertical.setY(y + length);
            PStart = new Point2D(x - width, y + length);
        }
        vertical.setWidth(width);
        vertical.setHeight(length + width);
        PEnd = new Point2D(x - width, PStart.getY() + length + width);

        vertical.setFill(new ImagePattern(image, 0, 0, 100, 100, false));
        return vertical;
    }

    /**
     *
     * @param x координата x начальной точки для отрисовки
     * @param y координата y начальной точки для отрисовки
     * @param image изображение ветви
     * @param down игиб вниз?
     * @param partLength длина изогнутого участка
     * @return группа с изображением изгиба ветви
     */
    public Group paintCurve(int x, int y, Image image, boolean down, double partLength) {
        double rightPart;
        Group curve = new Group();
        rightPart = partLength / Math.sqrt(2);
        // Верхняя часть
        QuadCurve quad = new QuadCurve();
        quad.setStartX(x + partLength);
        quad.setStartY(y);
        P0 = new Point2D(x + partLength, y);
        quad.setEndX(x + partLength + rightPart + width);
        if (down) {
            quad.setEndY(y - rightPart - width);
            P2 = new Point2D(x + partLength + rightPart + width, y - rightPart - width);
        } else {
            quad.setEndY(y + rightPart + width);
            P2 = new Point2D(x + partLength + rightPart + width, y + rightPart + width);
        }
        quad.setControlX(x + partLength + rightPart + width);
        if (down) {
            quad.setControlY(y);
            P1 = new Point2D(x + partLength + rightPart + width, y);
        } else {
            quad.setControlY(y);
            P1 = new Point2D(x + partLength + rightPart + width, y);
        }
        quad.setFill(new ImagePattern(image, 0, 0, 100, 100, false));

        // Нижняя часть
        QuadCurve lowquad = new QuadCurve();
        lowquad.setStartX(x + partLength + width);
        if (down) {
            lowquad.setStartY(y - width);
        } else {
            lowquad.setStartY(y + width);
        }
        lowquad.setEndX(x + partLength + rightPart);
        if (down) {
            lowquad.setEndY(y - rightPart);
        } else {
            lowquad.setEndY(y + rightPart);
        }
        lowquad.setControlX(x + partLength + rightPart);
        if (down) {
            lowquad.setControlY(y - width);
        } else {
            lowquad.setControlY(y + width);
        }

        lowquad.setFill(Color.WHITE);
        Shape shape = Shape.subtract(quad, lowquad);
        shape.setFill(new ImagePattern(image, 0, 0, 100, 100, false));
        curve.getChildren().addAll(shape);
        return curve;
    }

    /**
     * Нарисовать изогнутую ветвь
     * @param x координата x начальной точки для отрисовки
     * @param y координата y начальной точки для отрисовки
     * @param image изображение для заливки
     * @param down изгиб вниз?
     * @return группа с изображением ветви
     */
    public Group paintCurveBranch(int x, int y, Image image, boolean down) {
        Group branch = new Group();
        double control;
        int i;
        if (down) {
            i = -1;
        } else {
            i = 1;
        }
        // Верхняя часть
        QuadCurve quad = new QuadCurve();
        quad.setStartX(x);
        quad.setStartY(y);
        quad.setEndX(x + length);
        quad.setEndY(y);
        quad.setControlX(x + length / 2);
        if (down) {
            control = y + Math.tan(Math.toRadians(30)) * length / 2 + width;
            quad.setControlY(y + Math.tan(Math.toRadians(15)) * length / 2 + width);
        } else {
            control = y - Math.tan(Math.toRadians(30)) * length / 2 - width;
            quad.setControlY(y - Math.tan(Math.toRadians(30)) * length / 2 - width);
        }
        P0 = new Point2D(x, y);
        P2 = new Point2D(x + length, y);
        P1 = new Point2D(x + length / 2, control);
        quad.setFill(new ImagePattern(image, 0, 0, 100, 100, false));

        // Нижняя часть
        QuadCurve lowquad = new QuadCurve();
        lowquad.setStartX(x + width);

        lowquad.setStartY(y + i);
        lowquad.setEndX(x + length - width);
        lowquad.setEndY(y + i);

        lowquad.setControlX(x + length / 2);
        if (down) {
            lowquad.setControlY(y + Math.tan(Math.toRadians(30)) * length / 2);
        } else {
            lowquad.setControlY(y - Math.tan(Math.toRadians(30)) * length / 2);
        }

        lowquad.setFill(Color.WHITE);
        Shape shape = Shape.subtract(quad, lowquad);
        shape.setFill(new ImagePattern(image, 0, 0, 100, 100, false));
        branch.getChildren().addAll(shape);

        return branch;
    }

    /**
     * Нарисовать ветвь
     * @param x координата x начальной точки для отрисовки
     * @param y координата y начальной точки для отрисовки
     * @return группа с изображением ветви
     */
    public Group paint(int x, int y) {
        double rightPart;
        Group branch = new Group();

        double partLength = length / 3;
        rightPart = partLength / Math.sqrt(2);
        Image image;
        // Выбираем структуру коры
        if (texture == TrunkTexture.smooth) {
            image = new Image(BranchLevel.class.getResourceAsStream("pictures/smooth1.png"));
        } else if (texture == TrunkTexture.plastic) {
            image = new Image(BranchLevel.class.getResourceAsStream("pictures/plastic.png"));
        } else if (texture == TrunkTexture.smallCrack) {
            image = new Image(BranchLevel.class.getResourceAsStream("pictures/small.png"));
        } else {
            image = new Image(BranchLevel.class.getResourceAsStream("pictures/large.png"));
        }
        // Рисуем прямую ветку
        if (type == BranchType.Right) {
            branch.getChildren().add(paintHorizontal(x, y, length, image));
            if (repetCount > 0) {
                branch.getChildren().add(paintChildren(this));
            }
            paintLeaves(branch, 3);
        } else if (type == BranchType.Down) {
            if ("Вытянутая".equals(crownType)) {
                branch.getChildren().add(paintCurve(x, y, image, true, partLength));
                paintLeaves(branch, 1);
                branch.getChildren().add(paintVertical((int) (x + partLength + rightPart + width), (int) (y - rightPart - width), partLength, image, true));
                paintLeaves(branch, 2);
            } else {
                branch.getChildren().add(paintCurveBranch(x, y, image, true));
                paintLeaves(branch, 1);
            }

        } else {
            if (crownType.equals("Плакучая")) {
                branch.getChildren().add(paintCurve(x, y, image, false, partLength));
                // Рисуем вертикальную составляющую
                branch.getChildren().add(paintVertical((int) (x + partLength + rightPart + width), (int) (y + rightPart - partLength), partLength, image, false));
                paintLeaves(branch, 2);
            } else {
                // Рисуем изгиб
                branch.getChildren().add(paintCurveBranch(x, y, image, false));
                paintLeaves(branch, 1);
            }
        }
        return branch;
    }

    /**
     * Получить точку на кривой
     * @param t параметр кривых Безье
     * @return точка на кривой
     */
    public Point2D getPointOnCurve(double t) {
        return new Point2D(Math.pow((1 - t), 2.0) * P0.getX() + 2 * t * (1 - t) * P1.getX() + t * t * P2.getX(), Math.pow((1 - t), 2.0) * P0.getY() + 2 * t * (1 - t) * P1.getY() + t * t * P2.getY());
    }

    /**
     * Отрисовка листьев на ветви
     *
     * @param branch ветвь
     * @param flag тип ветви
     */
    public void paintLeaves(Group branch, int flag) {
        double c_max = 0;
        Point2D point;
        if (Crown.DENSITY != null && Leaf.IMAGE != null) {
            if (Crown.DENSITY == dense) {
                c_max = 0.75 + (int) (Math.random() * ((1 - 0.75) + 1));
            } else if (Crown.DENSITY == average) {
                c_max = 0.5 + (Math.random() * (0.75 - 0.5));
            } else if (Crown.DENSITY == easy) {
                do {
                    c_max = Math.random() * 0.5;
                } while (c_max == 0);
            }
            Leaf.setSize(length/4.0, length/8.0);
            int countLeafs = (int)((length/Leaf.HEIGHT_LEAF)* 2*c_max);
            double old = 0;
            double rand;
            Leaf leaf;
            Leaf.LEAFS.clear();
            for (int i = 0; i < countLeafs; i++) {
                if (flag == 1) { //изгиб
                    if (i == 0) {
                        old = 0;
                    }
                    do {
                        rand = old + (Math.random() * (length / (double) countLeafs));
                    } while (rand == 0);
                    point = getPointOnCurve(rand);
                    old += length / (double) countLeafs;
                    leaf = new Leaf(point, width, 1);
                } else if (flag == 2) { //вертикаль  
                    if (i == 0) {
                        old = PStart.getY();
                    }
                    do {
                        rand = old + (Math.random() * (length / (double) countLeafs));
                    } while (rand == 0);
                    old += length / (double) countLeafs;
                    point = new Point2D(PStart.getX(), rand);
                    leaf = new Leaf(point, width, 2);
                } else { //горизонталь
                    if (i == 0) {
                        old = PStart.getX();
                    }
                    do {
                        rand = old + (Math.random() * (length / (double) countLeafs));
                    } while (rand == 0);
                    old += length / (double) countLeafs;
                    point = new Point2D(rand, PStart.getY());
                    leaf = new Leaf(point, width, 1);
                    Leaf.addLeaf();
                }
            }
            Leaf.paint(branch);
        }
    }

    /**
     * Нарисовать дочерние ветви
     * @param branch ветвь- родитель
     * @return группа с изображениями дочерних ветвей
     */
    public Group paintChildren(Branch branch) {

        double x1;
        double x2;
        double xStart;
        double yStart;
        Group result = new Group();
        Group nextRotate = new Group();
        repetCount--;
        if (type == BranchType.Right) {
            for (int i = 0; i < branch.childrenCount; i++) {
                nextRotate = new Group();
                nextRotate.getChildren().add(paintChild(branch, branch.PStart.getX(), branch.PStart.getY(), false));

                // Поворот
                nextRotate.translateZProperty().bind(new SimpleDoubleProperty());

                nextRotate.setRotationAxis(new Point3D(0, 0, 1));

                nextRotate.setRotate(90 + angle);
                result.getChildren().add(nextRotate);
            }
        } else if (type == BranchType.Down) {
            if ("Вытянутая".equals(crownType)) {
                nextRotate = new Group();
                nextRotate.getChildren().add(paintChild(branch, branch.P0.getX(), branch.P0.getY() - width, true));

                // Поворот
                nextRotate.translateZProperty().bind(new SimpleDoubleProperty());

                nextRotate.setRotationAxis(new Point3D(0, 0, 1));

                nextRotate.setRotate(90 + angle);
                result.getChildren().add(nextRotate);
            } else {
                nextRotate = new Group();
                nextRotate.getChildren().add(paintChild(branch, branch.P0.getX(), branch.P0.getY() - width, false));

                // Поворот
                nextRotate.translateZProperty().bind(new SimpleDoubleProperty());

                nextRotate.setRotationAxis(new Point3D(0, 0, 1));

                nextRotate.setRotate(90 + angle);
                result.getChildren().add(nextRotate);
            }

        } else {
            if (crownType.equals("Плакучая")) {
                nextRotate = new Group();
                nextRotate.getChildren().add(paintChild(branch, branch.PStart.getX(), branch.PStart.getY(), true));

                // Поворот
                nextRotate.translateZProperty().bind(new SimpleDoubleProperty());

                nextRotate.setRotationAxis(new Point3D(0, 0, 1));

                nextRotate.setRotate(90 + angle);
                result.getChildren().add(nextRotate);
            } else {
                nextRotate = new Group();
                nextRotate.getChildren().add(paintChild(branch, branch.P0.getX(), branch.P0.getY(), false));

                // Поворот
                nextRotate.translateZProperty().bind(new SimpleDoubleProperty());

                nextRotate.setRotationAxis(new Point3D(0, 0, 1));

                nextRotate.setRotate(90 + angle);
                result.getChildren().add(nextRotate);
            }
        }
        return result;
    }

    /**
     * Нарисовать дочернюю ветвь
     * @param branch ветвь-родитель
     * @param startX координата x начальной точки для отрисовки
     * @param startY координата y начальной точки для отрисовки
     * @param vertical вертикальная ветвь?
     * @return
     */
    public Group paintChild(Branch branch, double startX, double startY, boolean vertical) {
        double x1;
        double x2;
        double xStart;
        double yStart;
        Group result = new Group();
        Group nextRotate = new Group();
        x1 = (branch.childrenCount - 1) * branch.length / branch.childrenCount;
        x2 = (branch.childrenCount) * branch.length / branch.childrenCount;
        xStart = Math.random() * (x2 - x1) + x1;
        yStart = Math.random();
        if (!vertical) {
            if (yStart < 0.5) {
                nextRotate.getChildren().add(branch.paint((int) (startX + xStart), (int) startY));
            } else {
                nextRotate.getChildren().add(branch.paint((int) (startX + xStart), (int) (startY + branch.width)));
            }
        } else {
            if (yStart < 0.5) {
                nextRotate.getChildren().add(branch.paint((int) (startX), (int) (startY + xStart)));
            } else {
                nextRotate.getChildren().add(branch.paint((int) (startX + width), (int) (startY + xStart)));
            }
        }
        // Поворот
        nextRotate.translateZProperty().bind(new SimpleDoubleProperty());

        nextRotate.setRotationAxis(new Point3D(0, 0, 1));

        nextRotate.setRotate(90 + angle);
        result.getChildren().add(nextRotate);
        return result;
    }
}
